﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.IO;

//Adapted from:
// http://stackoverflow.com/questions/137933/what-is-the-best-scripting-language-to-embed-in-a-c-desktop-application

namespace ScriptingInterface
{
    public interface IEdgeScript
    {
        int RunScript(int value);
    }
}

namespace EdgeScriptHelper
{
    public class ScriptHelper
    {
        private Dictionary<string, Assembly> LoadedScripts;
        public List<string> Messages;
        public ScriptHelper()
        {
            Messages = new List<string>();
        }

        /// <summary>
        /// Loads all .cs files in the specified directory, one directory allowed currently
        /// </summary>
        /// <param name="directory">The directory to load from.</param>
        /// <param name="force">Whether to force the dlls to be rebuilt or not.</param>
        public void LoadScripts(string directory, bool force)
        {
            LoadedScripts = new Dictionary<string, Assembly>();
            string[] filePaths = Directory.GetFiles(directory);
            foreach (string fp in filePaths)
            {
                if ((fp.Substring(fp.Length - 3) == ".cs") || (fp.Substring(fp.Length - 4) == ".dll"))
                {
                    StreamReader sr = new StreamReader(fp);
                    string code = sr.ReadToEnd();
                    sr.Close();

                    string h = "";
                    int i = fp.Length - 1;
                    int state = 0;
                    //get the file name, without the .cs
                    while ((i > 0) && (state < 3))
                    {
                        switch (state)
                        {
                            case 0: if (fp[i] == "."[0]) { state = 1; } break;
                            case 1: if (fp[i] != "\\"[0]) { h = fp[i] + h; } else { state = 3; } break;
                        }
                        i--;
                    }

                    if (!File.Exists("scripts\\" + h + ".dll") || force)
                    {
                        Assembly assm = CompileCode(code, h);
                        if (assm == null)
                        {
                            Messages.Add("[Critical Error] - " + h + " was not loaded.");
                        }
                        else
                        {
                            Messages.Add("[Debug] - Script " + h + " loaded" + (force == true ? " from file by force." : " from file."));
                        }
                        LoadedScripts.Add(h, assm);
                    }
                    else
                    {
                        LoadedScripts.Add(h, Assembly.LoadFrom(h + ".dll"));
                        Messages.Add("[Debug] - Script " + h + " loaded by dll.");
                    }

                }
            }
        }

        /// <summary>
        /// Runs a given script
        /// </summary>
        /// <param name="name"></param>
        /// <param name="parm"></param>
        /// <returns></returns>
        public int Run(string name, int parm)
        {
            if (!LoadedScripts.ContainsKey(name))
            {
                Messages.Add("[E] - Script " + name + " not found.");
                return -2;
            }
            return RunScript(LoadedScripts[name], parm);
        }

        private Assembly CompileCode(string code, string name)
        {
            CSharpCodeProvider csProvider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = false;
            parameters.OutputAssembly = "scripts\\" + name + ".dll";
            //TODO: Custom assembly list
            parameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

            CompilerResults result;
            result = csProvider.CompileAssemblyFromSource(parameters, code);
            if (result.Errors.HasErrors)
            {
                foreach (CompilerError e in result.Errors)
                {
                    Messages.Add(name + ": " + (e.IsWarning == true ? "WARNING: " : "ERROR: ") + e.ErrorText + " -- Line: " + e.Line.ToString());
                }
                return null;
            }
            return result.CompiledAssembly;
        }

        private int RunScript(Assembly script, int param)
        {
            //every class
            if (script == null)
            {
                return -3;
            }
            foreach (Type type in script.GetExportedTypes())
            {
                //every interface
                foreach (Type iface in type.GetInterfaces())
                {
                    //There was a script interface:
                    ConstructorInfo construct = type.GetConstructor(System.Type.EmptyTypes);
                    if (construct != null && construct.IsPublic)
                    {
                        ScriptingInterface.IEdgeScript scriptObject = construct.Invoke(null) as ScriptingInterface.IEdgeScript;
                        if (scriptObject != null)
                        {
                            return scriptObject.RunScript(param);
                        }
                        else
                        {
                            Messages.Add("[E] - Unable to run assembly: " + type.FullName);
                        }
                    }
                    else
                    {
                        Messages.Add("[E] - Unable to run script, there was no valid constructor. Constructors cannot take arguments. " + type.FullName);
                    }
                }
            }
            return -1; //nothing was run at all
        }
    }
}